<?php

namespace ltcms\route;

/**
 * 路由类
 * Class Router
 */
class Router
{
    public $method;
    public $uri;
    public $path;
    public $url;
    public $routeMap;
    public $routeRegMap;
    public $routeIndex=0;
    public $routeMethod="";
    private $cutMethod="";
    private $cutRoutePath="";
    private $groupMiddleware="";
    private $groupPrefix="";

    /**
     * 当前ROUTE参数
     * @var array
     */
    protected $route = [];

    private static $_instance;

    /**
     * REST定义
     * @var array
     */
    protected $rest = [
        'index'  => ['get', '', 'index'],
        'create' => ['get', '/create', 'create'],
        'edit'   => ['get', '/<id>/edit', 'edit'],
        'read'   => ['get', '/<id>', 'read'],
        'save'   => ['post', '', 'save'],
        'update' => ['put', '/<id>', 'update'],
        'delete' => ['delete', '/<id>', 'delete'],
    ];

    private function __construct(){
        $this->method = strtolower(request()->method());
        $this->uri = request()->query();
        $this->path = request()->pathinfo();
        $this->url = request()->url();
    }
    private function __clone(){ }

    /**
     * 获取类对象
     */
    public static  function getInstance(){
        if(!(self::$_instance instanceof self)){
            self::$_instance=new self();
        }
        return self::$_instance;
    }

    /**
     * 初始化配置
     */
    public function loader(){
        //遍历配置目录
        $routerDirs=array(
            LTCMS_ROOT."/route",
        );
        $routerDirs[]=LTCMS_APP."/route";
        if(defined("LTCMS_APP_NAME")){
            $routerDirs[]=LTCMS_APP."/".LTCMS_APP_NAME."/route";
        }
        foreach($routerDirs as $dir){
            $funcs=glob($dir."/*.php");
            if($funcs && is_array($funcs)){
                foreach($funcs as $file){
                    @include $file;
                }
            }
        }
    }

    /**
     * 设置路由变量
     * @access public
     * @param  array $route 路由变量
     * @return $this
     */
    public function setRoute(array $route)
    {
        $this->route      = $route;
        return $this;
    }

    /**
     * 派遣任务
     */
    public function dispatch()
    {
        $controller="";
        $action = "";
        $middleware=array();
        $args = array();
        $argsVal = array();
        $argsKey = array();
        $route = array();
        $isRoute=0;
        //查找路由中是否有匹配的路由
        if($this->path){
            //先匹配路由
            $routePath=rtrim($this->path,"/");
            if(LTCMS_ROUTE_MATCH=="2"){
                $controllerData=request()->getRouteData(true);
                $default_app=config("app.default_app","");
                if(LTCMS_APP_NAME==$default_app && $controllerData["app_name"]!=$default_app){
                    //全词匹配
                }else{
                    $routePath=$controllerData["route"];
                }
            }
            if(isset($this->routeMap[$this->method][$routePath])){
                $findRoute=$this->routeMap[$this->method][$routePath];
                $controller=$findRoute["controller"];
                if(isset($findRoute["middleware"])){
                    $middleware=$findRoute["middleware"];
                }
                if(isset($findRoute["route"])){
                    $route=$findRoute["route"];
                }
                $cnp=explode("/",$controller);
                $action=array_pop($cnp);
                $controller=implode("/",$cnp);
                $isRoute=1;
            }else{
                //匹配正则
                $routeRegMap=isset($this->routeRegMap[$this->method])?$this->routeRegMap[$this->method]:array();
                if($routeRegMap){
                    foreach($routeRegMap as $rgval){
                        $ok=preg_match("/^{$rgval["reg"]}$/",$routePath,$matchs);
                        if($ok > 0){
                            $controller=$rgval["controller"];
                            if(isset($rgval["middleware"])){
                                $middleware=$rgval["middleware"];
                            }
                            if(isset($rgval["route"])){
                                $route=$rgval["route"];
                            }
                            $cnp=explode("/",$controller);
                            $action=array_pop($cnp);
                            $controller=implode("/",$cnp);
                            $argsKey=$rgval["routeParams"];
                            foreach($matchs as $mkey=>$mval){
                                if($mkey==0){
                                    continue;
                                }
                                $argsVal[]=$mval;
                            }
                            $isRoute=1;
                            break;
                        }
                    }
                }
            }
            if(!$controller){
                //未匹配到则自动寻找控制器
                $default_controller=config("default_controller","Index");
                $default_action=config("default_action","index");
                if(LTCMS_ROUTE_MATCH=="2"){
                    $controllerData=request()->getControllerData(true);
                }else{
                    $controllerData=request()->getControllerData();
                }
                if(defined("LTCMS_APP_NAME")){
                    //多应用模式
                    $app_name=LTCMS_APP_NAME;
                    $controller=$controllerData["controller"]?$controllerData["controller"]:$default_controller;
                    $action=$controllerData["method"]?$controllerData["method"]:$default_action;
                    if(stripos($controller,"controller")===false){
                        $classNamespace="app\\{$app_name}\\controller\\".$controller;
                    }else{
                        $classNamespace=$controller;
                    }
                }else{
                    //单应用模式
                    $controller=$controllerData["controller"]?$controllerData["controller"]:$default_controller;
                    $action=$controllerData["method"]?$controllerData["method"]:$default_action;
                    if(stripos($controller,"controller")===false){
                        $classNamespace="app\\controller\\".$controller;
                    }else{
                        $classNamespace=$controller;
                    }
                }
            }else{
                if(defined("LTCMS_APP_NAME")){
                    $app_name=LTCMS_APP_NAME;
                    if(stripos($controller,"controller")===false){
                        $classNamespace="app\\{$app_name}\\controller\\".$controller;
                    }else{
                        $classNamespace=$controller;
                    }
                }else{
                    if(stripos($controller,"controller")===false){
                        $classNamespace="app\\controller\\".$controller;
                    }else{
                        $classNamespace=$controller;
                    }
                }
            }
        }else{
            if(isset($this->routeMap[$this->method][""])){
                $findRoute=$this->routeMap[$this->method][""];
                $controller=$findRoute["controller"];
                if(isset($findRoute["middleware"])){
                    $middleware=$findRoute["middleware"];
                }
                if(isset($findRoute["route"])){
                    $route=$findRoute["route"];
                }
                $cnp=explode("/",$controller);
                $action=array_pop($cnp);
                $controller=implode("/",$cnp);
                if(defined("LTCMS_APP_NAME")){
                    $app_name=LTCMS_APP_NAME;
                    if(stripos($controller,"controller")===false){
                        $classNamespace="app\\{$app_name}\\controller\\".$controller;
                    }else{
                        $classNamespace=$controller;
                    }
                }else{
                    if(stripos($controller,"controller")===false){
                        $classNamespace="app\\controller\\".$controller;
                    }else{
                        $classNamespace=$controller;
                    }
                }
                $isRoute=1;
            }else{
                //调用默认控制器配置
                $controller=config("default_controller","Index");
                $action=config("default_action","index");
                if(defined("LTCMS_APP_NAME")){
                    $app_name=LTCMS_APP_NAME;
                    if(stripos($controller,"controller")===false){
                        $classNamespace="app\\{$app_name}\\controller\\".$controller;
                    }else{
                        $classNamespace=$controller;
                    }
                }else{
                    if(stripos($controller,"controller")===false){
                        $classNamespace="app\\controller\\".$controller;
                    }else{
                        $classNamespace=$controller;
                    }
                }
            }
        }

        $is404=1;
        $response=null;
        if($classNamespace){
            if($argsKey){
                $keyCount=count($argsKey);
                $argsVal=array_pad($argsVal,$keyCount,null);
                $args=array_combine($argsKey,$argsVal);
            }
            $args=array_merge($args,$route);
            $classNamespace=str_replace("/","\\",$classNamespace);
            //在检测是否有存在的类
            if(class_exists($classNamespace)){
                $controllerObj=$this->make($classNamespace);
                if(method_exists($controllerObj,$action)){
                    $is404=0;
                    if($isRoute=="0"){
                        $doc=$this->getMethodDoc($classNamespace,$action);
                        $docMap=$this->getParseDoc($doc);
                        $method=$docMap["method"];
                        if($method && $method != $this->method){
                            $is404=1;
                        }
                    }
                    if($is404=="0"){
                        request()->setController($classNamespace)->setAction($action)->setRoute($args);
                        $next = function($request) use($classNamespace,$action,$args){
                            event()->trigger("http_before.{$classNamespace}");
                            $run_result=$this->makeMethod($classNamespace, $action,$args);
                            event()->trigger("http_after.{$classNamespace}");
                            return  $run_result;
                        };
                        $response=middleware()->dispatch($next,$middleware);
                    }
                }
            }
        }
        if($is404=="1"){
            $route_miss=config("app.route_miss","");
            if($route_miss && class_exists($route_miss)){
                $controllerObj=$this->make($route_miss);
                if(method_exists($controllerObj,"handle")){
                    request()->setController($route_miss)->setAction($action);
                    $errorData=array(
                        "classNamespace"=>$classNamespace,
                        "action"=>$action,
                    );
                    $args=array(
                        "data"=>$errorData,
                        "code"=>404,
                    );
                    return  $this->makeMethod($route_miss, $action,$args);
                }
            }
            throw new \RuntimeException($classNamespace,404);
        }
        return $response;
    }

    /**
     * 反射创建类实例
     * @param $className
     * @return mixed
     */
    public function make($className)
    {
        $reflectionClass = new \ReflectionClass($className);
        $constructor = $reflectionClass->getConstructor();
        $dependencies=array();
        if($constructor){
            $parameters  = $constructor->getParameters();
            if($parameters){
                $dependencies = $this->getDependencies($parameters);
            }
        }
        return $reflectionClass->newInstanceArgs($dependencies);
    }

    /**
     * 解析文档
     * @param $doc
     * @return array
     */
    public function getParseDoc($doc){
        $result=array();
        if($doc){
            $dosLines=explode("\n",$doc);
            if($dosLines){
                foreach($dosLines as $dval){
                    if(stripos($dval,"@") !== false){
                        $tmp=trim(stristr($dval,"@"));
                        list($key,$val)=array_pad(explode("：",str_replace("@","",$tmp)),2,"");
                        if($key && $val){
                            $result[$key]=$val;
                        }
                    }
                }
            }
        }
        return $result;
    }

    /**
     * 获取方法注释
     */
    public function getMethodDoc($classNamespace,$action){
        $reflectionMethod = new \ReflectionMethod($classNamespace, $action);
        $doc=$reflectionMethod->getDocComment();
        return $doc;
    }

    /**
     * 反射创建方法
     * @param $className
     * @return mixed
     */
    public function makeMethod($classNamespace,$action,$args=array())
    {
        $controllerObj=$this->make($classNamespace);
        $reflectionMethod = new \ReflectionMethod($classNamespace, $action);

        $dependencies=array();
        $parameters  = $reflectionMethod->getParameters();
        if($parameters){
            $dependencies = $this->getDependencies($parameters);
        }
        if($dependencies){
            foreach($dependencies as $dkey=>$dval){
                if(isset($args[$dkey])){
                    $dependencies[$dkey]=$args[$dkey];
                }
            }
        }
        return $reflectionMethod->invokeArgs($controllerObj, $dependencies);
    }

    /**
     * 依赖解析
     * @param $parameters
     * @return array
     */
    public function getDependencies($parameters)
    {
        $dependencies = [];
        foreach($parameters as $parameter) {
            $type = $parameter->getType();
            if(is_null($type)){
                if($parameter->isDefaultValueAvailable()) {
                    $dependencies[$parameter->name] = $parameter->getDefaultValue();
                } else {
                    $dependencies[$parameter->name] = null;
                }
            }else{
                $className = $type->getName();
                $dependencies[$parameter->name] = $this->make($className);
            }
        }
        return $dependencies;
    }

    /**
     * 路由注册
     */
    public function register($method,$routePath,$controller){
        $routePath=$this->groupPrefix?($routePath?$this->groupPrefix."/".$routePath:$this->groupPrefix):$routePath;
        if(strpos($routePath,"<") ===false){
            //非正则匹配
            $this->routeMap[$method][$routePath]=array(
                "controller"=>$controller,
            );
            if($this->groupMiddleware){
                if(isset($this->routeMap[$method][$routePath]["middleware"]) && $this->routeMap[$method][$routePath]["middleware"]){
                    $this->routeMap[$method][$routePath]["middleware"]=array_merge($this->routeMap[$method][$routePath]["middleware"],$this->groupMiddleware);
                }else{
                    $this->routeMap[$method][$routePath]["middleware"]=$this->groupMiddleware;
                }
            }
            if($this->route){
                $this->routeMap[$method][$routePath]["route"]=$this->route;
                $this->route=array();
            }
        }else{
            //正则匹配路由
            $routePathIndex=md5($routePath);
            $ok=preg_match_all("/(<[^>]+>)/",$routePath,$matchs);
            if($ok >0){
                $searchZwf=array(
                    "/(<[^>]+>)/",
                );
                $routePathZwf=preg_replace_callback($searchZwf,function ($matchs){
                    $value=$matchs[1];
                    $value=trim($value,"<>");
                    if($value=="*"){
                        return "@xing@";
                    }else{
                        list($field,$reg)=array_pad(explode("|",$value),2,"");
                        return "@{$field}@";
                    }
                },$routePath);

                $routeParams=array();
                $replace=array();
                $search=array();
                foreach($matchs as $mkey=>$mval){
                    if($mkey=="0"){

                    }else{
                        foreach($mval as $zval){
                            $value=trim($zval,"<>");
                            if($value=="*"){
                                $replace[]="(.*?)";
                                $search[]="@xing@";
                            }else{
                                list($field,$reg)=array_pad(explode("|",$value),2,"");
                                $routeParams[]=$field;
                                $replace[]="({$reg})";
                                $search[]="@{$field}@";
                            }
                        }
                    }
                }
                $regExp=preg_quote($routePathZwf,"/");
                $regExp=str_replace($search,$replace,$regExp);

                $this->routeRegMap[$method][$routePathIndex]=array(
                    "controller"=>$controller,
                    "reg"=>$regExp,
                    "routeParams"=>$routeParams,
                );
                if($this->route){
                    $this->routeRegMap[$method][$routePathIndex]["route"]=$this->route;
                    $this->route=array();
                }
            }
            if($this->groupMiddleware){
                if(isset($this->routeRegMap[$method][$routePathIndex]["middleware"]) && $this->routeRegMap[$method][$routePathIndex]["middleware"]){
                    $this->routeRegMap[$method][$routePathIndex]["middleware"]=array_merge($this->routeRegMap[$method][$routePathIndex]["middleware"],$this->groupMiddleware);
                }else{
                    $this->routeRegMap[$method][$routePathIndex]["middleware"]=$this->groupMiddleware;
                }
            }
        }

        $this->cutMethod=$method;
        $this->cutRoutePath=$routePath;
    }

    /**
     * get 请求
     */
    public function get($routePath,$controller){
        $this->register("get",$routePath,$controller);
        return $this;
    }

    /**
     * post 请求
     */
    public function post($routePath,$controller){
        $this->register("post",$routePath,$controller);
        return $this;
    }

    /**
     * put 请求
     */
    public function put($routePath,$controller){
        $this->register("put",$routePath,$controller);
        return $this;
    }

    /**
     * delete 请求
     */
    public function delete($routePath,$controller){
        $this->register("delete",$routePath,$controller);
        return $this;
    }

    /**
     * patch 请求
     */
    public function patch($routePath,$controller){
        $this->register("patch",$routePath,$controller);
        return $this;
    }

    /**
     * head 请求
     */
    public function head($routePath,$controller){
        $this->register("head",$routePath,$controller);
        return $this;
    }

    /**
     * options 请求
     */
    public function options($routePath,$controller){
        $this->register("options",$routePath,$controller);
        return $this;
    }

    /**
     * 获取路由映射
     * @return mixed
     */
    public function getRouteMap(){
        return $this->routeMap;
    }

    /**
     * 设置中间件
     */
    public function middleware($middleware){
        $middleware=is_array($middleware)?$middleware:array($middleware);
        if(strpos($this->cutRoutePath,"<")===false){
            if(isset($this->routeMap[$this->cutMethod][$this->cutRoutePath]["middleware"])){
                $this->routeMap[$this->cutMethod][$this->cutRoutePath]["middleware"]=array_merge($this->routeMap[$this->cutMethod][$this->cutRoutePath]["middleware"],$middleware);
            }else{
                $this->routeMap[$this->cutMethod][$this->cutRoutePath]["middleware"]=$middleware;
            }
        }else{
            $routePathIndex=md5($this->cutRoutePath);
            if(isset($this->routeRegMap[$this->cutMethod][$routePathIndex]["middleware"])){
                $this->routeRegMap[$this->cutMethod][$routePathIndex]["middleware"]=array_merge($this->routeRegMap[$this->cutMethod][$routePathIndex]["middleware"],$middleware);
            }else{
                $this->routeRegMap[$this->cutMethod][$routePathIndex]["middleware"]=$middleware;
            }
        }
    }

    /**
     * 设置group中间件
     */
    public function group($prefix,$func="",$middleware=null){
        if($prefix instanceof \Closure){
            if($func){
                $this->groupMiddleware=is_array($func)?$func:array($func);
            }
            $prefix();
            $this->groupMiddleware="";
            $this->groupPrefix="";
        }else{
            if($func){
                $this->groupPrefix=$prefix;
                if($middleware){
                    $this->groupMiddleware=is_array($middleware)?$middleware:array($middleware);
                }
                $func();
                $this->groupMiddleware="";
                $this->groupPrefix="";
            }
        }
    }
}

